/*
 * Copyright 2007-2022 Enrico Boldrini, Lorenzo Bigagli This file is part of
 * CheckboxTree. CheckboxTree is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version. CheckboxTree is distributed in the hope that it
 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details. You should have received a copy of the GNU
 * General Public License along with CheckboxTree; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA
 */
package com.element.ui.tree.checkboxtree;


import javax.swing.*;
import javax.swing.plaf.ActionMapUIResource;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

/**
 * Checkbox with four states. Available states are UNCHECKED, CHECKED,
 * GREY_CHECKED, GREY_UNCHECKED. The rendering is obtained via a visualization
 * hack. The checkbox exploits the different rendering (greyed) of checkbox
 * pressed, thus the press, arm, rollover events are not available.
 * <p>
 * Maintenance tip - Some tricks are needed to get this code working:
 * <ol>
 *     <li>You have to override addMouseListener() to do nothing</li>
 *     <li>You have to add a mouse event on mousePressed by calling super.addMouseListener()</li>
 *     <li>You have to replace the UIActionMap for the keyboard event "pressed" with your own one.</li>
 *     <li>You have to remove the UIActionMap for the keyboard event "released".</li>
 *     <li>You have to grab focus when the next state is entered, otherwise clicking on the component will not get the focus.</li>
 * </ol>
 *
 * @author boldrini
 * @author bigagli
 */
public class QuadristateCheckbox extends JCheckBox {

	public QuadristateCheckbox() {
		this(null);
	}

	public QuadristateCheckbox(String text) {
		this(text, QuadristateButtonModel.State.UNCHECKED);
	}

	public QuadristateCheckbox(String text, Icon icon, QuadristateButtonModel.State state) {
		super(text, icon);
		// Add a listener for when the mouse is pressed
		super.addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				grabFocus();
				getModel().nextState();
			}
		});
		// Reset the keyboard action map
		ActionMap map = new ActionMapUIResource();
		map.put("pressed", new AbstractAction() {
			public void actionPerformed(ActionEvent e) {
				grabFocus();
				getModel().nextState();
			}
		});
		map.put("released", null);
		SwingUtilities.replaceUIActionMap(this, map);
		setState(state);
	}

	public QuadristateCheckbox(String text, QuadristateButtonModel.State initial) {
		this(text, null, initial);
	}

	/** No one may add mouse listeners, not even Swing! */
	@Override
	public void addMouseListener(MouseListener l) {
	}

	@Override
	public QuadristateButtonModel getModel() {
		return (QuadristateButtonModel) super.getModel();
	}

	/**
	 * Return the current state, which is determined by the selection status of
	 * the model.
	 */
	public QuadristateButtonModel.State getState() {
		return getModel().getState();
	}

	@Override
	protected void init(String text, Icon icon) {
		// substitutes the underlying checkbox model:
		// if we had call setModel an exception would be raised
		// because setModel calls a getModel that return a
		// QuadristateButtonModel, but at this point we
		// have a JToggleButtonModel
		this.model = new QuadristateButtonModel();
		super.setModel(this.model);// side effect: set listeners
		super.init(text, icon);
	}

	public void setModel(QuadristateButtonModel model) {
		super.setModel(model);
	}

	/**
	 * Set the new state to either CHECKED, UNCHECKED or GREY_CHECKED. If state
	 * == null, it is treated as GREY_CHECKED.
	 */
	public void setState(QuadristateButtonModel.State state) {
		getModel().setState(state);
	}

}
